using System;
using System.Collections.Generic;
using System.Text;
using RoardersAgentSim.Map;
using System.Linq;
using System.Configuration;
using System.IO;

namespace RoardersAgentSim.Agent
{
    //-----------------------------------------------------------------------------------------------------
    /// <summary>
    /// Population class to define a group of people with a specific behavior
    /// </summary>
    public class Population
    {
        // input attributes
        #region input attributes
        /// <summary>
        /// home zone
        /// </summary>
        public int r;
        /// <summary>
        /// location
        /// </summary>
        public Zone h_p;
        /// <summary>
        /// Access to car (1) or not (0)
        /// </summary>
        public int phi;
        /// <summary>
        /// Tendency to follow advice 
        /// </summary>
        public double zeta;
        /// <summary>
        /// Field effect by surrounding environment (herd instinct)
        /// </summary>
        public double gamma;      // field effect - influence of neighbours
        /// <summary>
        /// Value determining if the population need to go to a shelter (0) or hospital (1)
        /// </summary>
        public int eta;// needs shelter - 0 or hospital - 1
        /// <summary>
        /// position on link 
        /// </summary>
        public double lambda = 0;     // position on link - 
        //parameters in utility functions
        public double beta_i;
        public double beta_d;
        public double beta_u;
        #endregion
        #region derived attributes
        /// <summary>
        /// distance from current location to shelter j
        /// </summary>
        public double[] d;         // distance from current location for individual i to shelter j
        /// <summary>
        /// number of neighbors of individual who started evacuating
        /// </summary>
        public int m_e;           // number of neighbors of individual i who started evacuating
        /// <summary>
        /// number of neighbors of individual who started evacuating
        /// </summary>
        public int previous_m_e;           // number of neighbors of individual i who started evacuating
        /// <summary>
        /// number of neighbors of individual who started evacuating to shelter j->the key
        /// </summary>
        public Dictionary<int, int> m_s;          // number of neighbors of individual i who started evacuating to shelter j
        public Dictionary<int, int> previous_m_s;
        /// <summary>
        /// time individual reaches shelter
        /// </summary>
        public double tau;          // time individual i reaches shelter
        /// <summary>
        /// evacuation decision (yes = 1/ no = 0)
        /// </summary>
        public int e;             // evacuation decision (yes = 1 / no = 0)
        /// <summary>
        /// shelter chosen to go to (overriden by the use of property Shelter)
        /// </summary>
        //public int s;             // shelter chosen to go to
        /// <summary>
        /// Shelter chosen to go to - property
        /// </summary>
        public Shelter Shelter { get; set; }

        /// <summary>
        /// set of links (set of links (OD specific path and mode chosen) 
        /// -overriden by property LinkPath
        /// </summary>
        //public int[] P;             // set of links (OD specific path and mode) chosen
        /// <summary>
        /// set of links (set of links (OD specific path and mode chosen) 
        /// </summary>
        public LinkPath LinkPath { get; set; }

        /// <summary>
        /// still in simulation - 1, reached shelter or not evacuating at all - 0
        /// </summary>
        public int active;        // still in simulation - 1, reached shelter or not evacuating at all - 0
        /// <summary>
        /// Used in the calculation for A1.1
        /// </summary>
        private double alfa;

        #endregion

        #region export attributes

        private bool initialNode = true;

        /// <summary>
        /// Travel time
        /// </summary>
        public double TravelTime
        {
            get
            {
                return this.tau;
            }
        }
        /// <summary>
        /// Initial demand node
        /// </summary>
        public string InitialNodeId
        {
            private set;
            get;
        }

        #endregion


        #region population properties
        /// <summary>
        /// Number of people considered in the population group
        /// </summary>
        public int PeopleCount { get; private set; }
        /// <summary>
        /// The current node of the population
        /// </summary>
        public Node CurrentNode;
        /// <summary>
        /// Current Link (null if the lamba == 0 and the population is not in transit
        /// </summary>
        public RoadLink CurrentLink;
        public bool shelterReached;
        /// <summary>
        /// 
        /// </summary>
        public LinkType SelectedMode { get; set; }

        private PopulationManager manager;

        #endregion
        #region constructor
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="pop"></param>
        public Population(PopulationManager manager, CSVUtils.CSVMapping.PopulationMapping pop, int size = -1)
        {
            if (size < 0)
                this.PeopleCount = pop.Count;
            else
                this.PeopleCount = size;
            this.phi = pop.phi;
            this.zeta = pop.zeta;
            this.gamma = pop.gamma;
            this.eta = pop.eta;
            this.alfa = pop.evacuationThreshold;
            this.beta_d = double.Parse(ConfigurationManager.AppSettings["BETAD"]);
            this.beta_i = double.Parse(ConfigurationManager.AppSettings["BETAI"]);
            this.beta_u = double.Parse(ConfigurationManager.AppSettings["BETAU"]);
            this.manager = manager;
            m_s = new Dictionary<int, int>();
            previous_m_s = new Dictionary<int, int>();
            m_e = 0;
            previous_m_e = 0;
            active = 1;
            this.tau = -1;
            this.InitialNodeId = pop.NodeID;
        }
        #endregion

        #region internal methods
        /// <summary>
        /// set the current location
        /// </summary>
        /// <param name="node"></param>
        internal void SetCurrentLocation(Node node)
        {
            this.h_p = node.Zone;
            this.CurrentNode = node;
        }
        /// <summary>
        /// check if the population is evacuating
        /// </summary>
        /// <returns></returns>
        internal bool IsEvacuating()
        {
            return this.e == 1;
        }
        /// <summary>
        /// Check if the population is in transit
        /// </summary>
        /// <returns></returns>
        internal bool IsInTransit()
        {
            if (lambda != 0 && CurrentLink != null)
                return true;
            return false;
        }
        /// <summary>
        /// Function to determine if the indicated population is neighbor with the current one
        /// </summary>
        /// <param name="?"></param>
        /// <returns></returns>
        internal bool IsNeighborWith(Population pop)
        {
            if (this.CurrentNode == pop.CurrentNode)
                return true;
            if (this.CurrentNode.IsNeighborWith(pop.CurrentNode))
                return true;

            return false;
        }

        /// <summary>
        /// Function to Save the state in a history
        /// </summary>
        internal void SaveStateHistory()
        {
            this.previous_m_e = m_e;
            this.previous_m_s = m_s;
        }
        #endregion
        #region calculation methods
        /// <summary>
        /// Determine whether the population is evacuating
        /// </summary>
        /// <param name="env"></param>
        /// <param name="evacuationAdvice"></param>
        /// <returns></returns>
        public bool DetermineIfEvacuating(Environment env, Advice evacuationAdvice) // A1.1
        {
            LogHelper.LogStep("Determine if evacuating - A1.1", true);
            if (evacuationAdvice != null)
                LogHelper.LogStep("Evacuation advice " + evacuationAdvice.Description, true);
            else
                LogHelper.LogStep("Empty evacuation advice ", true);
            // for all individuals i with e == 0 and active = 1
            // compute ue
            double temp = this.beta_i * this.eta + this.beta_d * this.h_p.d_h + this.beta_u * env.tau_z;
            double ue = temp + this.zeta * this.previous_m_e + this.gamma * (evacuationAdvice == null ? 0 : evacuationAdvice.e_a);
            if (ue > this.alfa)
                this.e = 1;
            // when e becomes 1, call MakeShelterDecision() - this is done in the manager
            else
                this.e = 0;
            // or probabilistically
            //TODO future implementation...we will do it probabilistically
            bool future = false;
            if (future)
            {
                double prob = 1 / (Math.Exp(this.alfa - ue));
                double rnd = Utils.NextDouble(0, 1);
                if (rnd < prob)
                    this.e = 0;
                else
                    this.e = 1;
            }
            if (this.e == 1)
            {
                LogHelper.LogStep("Decision = evacuating", true);
                return true;
            }
            LogHelper.LogStep("Decision = not evacuating", true);
            return false;
        }
        /// <summary>
        /// Choose the Path
        /// </summary>
        internal void ChoosePath()
        {
            LogHelper.LogStep("Choosing path", true);
            this.LinkPath = null;
            try
            {
                this.LinkPath = Algorithms.CalculateOptimalCostPath(this.SelectedMode, this.CurrentNode, this.Shelter, this, manager.environment.CurrentMap);
                if (this.LinkPath != null)
                    LogHelper.LogStep("Found path: " + this.LinkPath.Description, true);
                else
                    LogHelper.LogStep("Found empty path: ", true);
            }
            catch (Exception ex)
            {
                LogHelper.LogException(ex);
            }
        }

        public double GetZetaBar(RoadLink rl)
        {
            int currentZoneID = this.h_p.ZoneID;
            double zetabar = 0;
            if (manager.advicesManager.ContainsKey(currentZoneID) && manager.advicesManager[currentZoneID].P_a.RoadLinks.Contains(rl))
                zetabar = this.zeta;
            return zetabar;
        }


        //-----------------------------------------------------------------------------------------------------
        public void Move() // A1.4
        {
            LogHelper.LogStep("Move - A1.4", true);

            if (lambda != 0)
            {
                LogHelper.LogStep("In transit", true);
                //the population is in transit (is on the link)
                if (this.CurrentLink == null)
                {
                    LogHelper.LogError("No current link was found but even if the population group is in transit");
                    return;
                }
                this.lambda += this.CurrentLink.v_kt * Utils.GetTimeConversion();
                this.CurrentLink.UpdateFlow(0);
            }
            else
            {
                if (this.LinkPath == null || !this.LinkPath.RoadLinks.Any())
                {
                    if (this.Shelter.Node != this.CurrentNode)
                        LogHelper.LogStep("Population in node " + this.CurrentNode.NodeID + " has not found a path to shelter  " + this.Shelter.ShelterID, true);
                }
                else
                {
                    this.CurrentLink = this.LinkPath.RoadLinks.FirstOrDefault();
                    if (this.CurrentLink.IsBlocked())
                    {
                        LogHelper.LogStep("Population in node " + this.CurrentNode.NodeID + " cannot move because " + this.CurrentLink.Description + " is blocked", true);
                        this.CurrentLink = null;
                    }
                    else
                    {
                        if (initialNode)
                        {
                            this.CurrentNode.DepartureTime = this.manager.environment.time;
                            initialNode = false;
                        }
                        lambda = this.CurrentLink.v_kt * Utils.GetTimeConversion();
                        //update the flow accordingly
                        this.CurrentLink.UpdateFlow(this.PeopleCount);
                        this.CurrentLink.q_hsk[this.Shelter.ShelterID] += this.PeopleCount;
                    }
                }
            }
            if(CurrentLink!=null)
                LogHelper.LogStep("Current link " + CurrentLink.Description, true);
            if (CurrentLink != null && lambda >= CurrentLink.Link.Length)
            {
                //update the link flow information accordingly
                this.CurrentLink.UpdateFlow((-1) * this.PeopleCount);
                this.CurrentLink.q_hsk[this.Shelter.ShelterID] -= this.PeopleCount;
                this.CurrentNode = this.CurrentLink.Link.Destination;
                this.h_p = this.CurrentNode.Zone;
                this.lambda = 0;
                this.CurrentLink = null;
            }

            // update h_p
            // if i reaches shelter, call AgentReachesShelter(i)
            LogHelper.LogStep("Current node " + CurrentNode.NodeID, true);
            if (CurrentNode.HasShelter)//what should we do if the shelter reached is not the one in question?
            {
                LogHelper.LogStep("Current node has shelter", true);
                this.shelterReached = AgentReachesShelter();
                if (!shelterReached)
                    manager.MakeShelterDecision(this);
            }
        }
        //-----------------------------------------------------------------------------------------------------
        public bool AgentReachesShelter() // A1.5
        {
            if (this.Shelter.zr > 0 && this.Shelter.zr >= this.PeopleCount)
            {
                this.active = 0;
                this.Shelter.UpdateResidualCapacity(this.PeopleCount);
                LogHelper.LogStep("Reached " + this.Shelter.Description, true);
                return true;
            }
            LogHelper.LogStep("Cannot reach. " + this.Shelter + " unavailable", true);
            return false;
            // if zr > 0 then active[i] = 0, tau[i] = simulation time
            // else call MakeShelterDecision(i)
        }
        #endregion

        internal void UpdateTravelTime(int time)
        {
            this.tau = time;
            this.Shelter.UpdateTravelTime(time);
        }
    }
    //-----------------------------------------------------------------------------------------------------
    /// <summary>
    /// The population manager
    /// a collection (list) of population groups
    /// </summary>
    public class PopulationManager
    {
        private List<Population> populationCollection;
        internal Environment environment;
        private RoadLinkManager roadLinks;
        private ShelterManager shelterManager;
        internal AdviceManager advicesManager;
        //-----------------------------------------------------------------------------------------------------
        #region constructors
        /// <summary>
        ///
        /// </summary>
        /// <param name="environment"></param>
        /// <param name="roadLinks"></param>
        /// <param name="shelters"></param>
        /// <param name="advice"></param>
        public PopulationManager(Environment environment, RoadLinkManager roadLinks, ShelterManager shelters, AdviceManager advice)
        {
            // TODO: Complete member initialization
            this.environment = environment;
            this.roadLinks = roadLinks;
            this.shelterManager = shelters;
            this.advicesManager = advice;
        }
        #endregion

        #region public population methods
        /// <summary>
        /// Determine foreach population if it i evacuating
        /// </summary>
        public void DetermineIfEvacuating() // A1.1
        {
            LogHelper.LogStep("Population manager: Determine If Evacuating - A1.1");
            foreach (Population population in populationCollection.Where(x => x.active == 1))
            {
                LogHelper.LogStep("A1.1 for population group from demand node " + population.InitialNodeId, true);
                population.SaveStateHistory();
                //if the population is in transit, just move
                if (population.IsInTransit())
                    continue;
                Advice evacuationAdvice = null;
                if (this.advicesManager.ContainsKey(population.h_p.ZoneID))
                    evacuationAdvice = this.advicesManager[population.h_p.ZoneID];
                else
                    LogHelper.LogStep(String.Format("No advice for zone {0}", population.h_p.Description));

                population.DetermineIfEvacuating(this.environment, evacuationAdvice);
                if (population.IsEvacuating())
                    this.MakeShelterDecision(population);
                LogHelper.LogStep("END A1.1", true);
            }

            //update now the number of neighbors of individual who started evacuating
            foreach (Population pop in populationCollection)
            {
                pop.m_s.Clear();
                var query = populationCollection.Where(x => x.IsEvacuating() && pop.IsNeighborWith(x));
                if (query == null || !query.Any())
                    pop.m_e = 0;
                else
                {
                    pop.m_e = query.Count();
                    foreach (Shelter s in this.shelterManager.Shelters)
                    {
                        var shelterQuery = query.Where(x => x.Shelter.ShelterID == s.ShelterID);
                        if (shelterQuery == null || !shelterQuery.Any())
                            pop.m_s.Add(s.ShelterID, 0);
                        else
                            pop.m_s.Add(s.ShelterID, shelterQuery.Count());
                    }
                }
            }
        }

        #endregion
        //-----------------------------------------------------------------------------------------------------
        /// <summary>
        /// Make shelter decision for each population group
        /// </summary>
        /// <param name="p"></param>
        public void MakeShelterDecision(Population p) // A1.2
        {
            LogHelper.LogStep("Making Shelter Decision - A1.2", true);
            double max = -1;
            int max_shelterID = -1;
            p.shelterReached = false;
            foreach (Shelter shelter in shelterManager.Shelters.Where(x => !x.IsFull))
            {
                if (p.h_p == null)
                {
                    LogHelper.LogError("No zone defined for this population");
                    continue;
                }
                double min_walk = this.roadLinks.MinWalk(p.h_p.ZoneID, shelter.ShelterID);
                double min_car;
                double g_bar;
                g_bar = min_walk;
                if (p.phi == 1)
                {
                    min_car = this.roadLinks.MinCar(p.h_p.ZoneID, shelter.ShelterID);
                    if (!(min_walk < min_car))
                        g_bar = min_car;
                }
                double zetabar = 0;
                if (advicesManager.ContainsKey(p.h_p.ZoneID) && advicesManager[p.h_p.ZoneID].S_a.Contains(shelter))
                    zetabar = p.zeta;
                double us = (p.beta_i * p.eta) * shelter.psi + p.beta_d * g_bar + p.gamma * (p.previous_m_s.ContainsKey(shelter.ShelterID) ? p.previous_m_s[shelter.ShelterID] : 0) + zetabar;
                if (us > max)
                {
                    max = us;
                    max_shelterID = shelter.ShelterID;
                    p.Shelter = shelter;
                }
            }
            if (max_shelterID >= 0)
            {
                LogHelper.LogStep("Chosen " + p.Shelter.Description, true);
            }
            ChooseModeAndPath(p);
        }
        /// <summary>
        /// Choose the mode and path
        /// </summary>
        /// <param name="p"></param>
        private void ChooseModeAndPath(Population p)
        {
            LogHelper.LogStep("Choosing the mode and path- A1.3", true);

            //choose the mode by going through all the outgoing links

            double min_cost = double.MaxValue;
            int currentZoneID = p.h_p.ZoneID;
            double zetabar = 0;
            LogHelper.LogStep("Choosing the mode", true);
            if (p.e == 1)
            {
                zetabar = 0;
                LinkType chosen = LinkType.CAR_LINK;
                foreach (RoadLink rl in p.CurrentNode.A_minus_Car)
                {
                    zetabar = p.GetZetaBar(rl);
                    double temp_car = rl.getCKCar(zetabar);
                    if (min_cost > temp_car)
                    {
                        min_cost = temp_car;
                    }
                }

                foreach (RoadLink rl in p.CurrentNode.A_minus_Walk)
                {
                    zetabar = p.GetZetaBar(rl);
                    double temp_walk = rl.getCKWalk(zetabar);
                    if (min_cost > temp_walk)
                    {
                        min_cost = temp_walk;
                        chosen = LinkType.WALK_LINK;
                    }
                }

                p.SelectedMode = chosen;
                switch (chosen)
                {
                    case LinkType.CAR_LINK:
                        LogHelper.LogStep("Chosen car mode", true);
                        break;
                    default:
                        LogHelper.LogStep("Chosen walk mode", true);
                        break;

                }
                ChoosePath(p);
            }

        }
        /// <summary>
        /// Choose Path should be called periodically but where?
        /// </summary>
        /// <param name="p"></param>
        private void ChoosePath(Population p)
        {
            p.ChoosePath();
        }

        /// <summary>
        /// Get the number of people on the specified link at moment t
        /// </summary>
        /// <param name="rl"></param>
        public int GetFlowOnLink(RoadLink rl)
        {
            var query = populationCollection.Where(x => x.IsInTransit() && x.CurrentLink.Link.LinkID == rl.Link.LinkID);
            if (query == null || !query.Any())
                return 0;
            return query.Sum(x => x.PeopleCount);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="init"></param>
        internal void Initialize(CSVUtils.Initializer init)
        {
            int groupSize = PopulationManager.GROUPSIZE;
            var list = init.ParsePopulation();
            populationCollection = new List<Population>();
            foreach (CSVUtils.CSVMapping.PopulationMapping pop in list)
            {

                if (pop.Count > groupSize)
                {
                    int k = pop.Count / groupSize;
                    if (pop.Count % groupSize != 0)
                        k++;
                    int partialSize = groupSize;

                    for (int i = 0; i < k; ++i)
                    {
                        pop.Count -= partialSize;
                        Population p = new Population(this, pop, partialSize);
                        Node node = environment.GetNode(pop.NodeID);
                        p.SetCurrentLocation(node);
                        populationCollection.Add(p);
                        if (pop.Count > groupSize)
                            partialSize = groupSize;
                        else
                            partialSize = pop.Count;
                    }

                }
                else
                {
                    Population p = new Population(this, pop);
                    Node node = environment.GetNode(pop.NodeID);
                    p.SetCurrentLocation(node);
                    populationCollection.Add(p);
                }
            }

            //initialize for each zone the centroid node

        }
        //-----------------------------------------------------------------------------------------------------
        /// <summary>
        /// Move all the population
        /// </summary>
        public void Move()
        {
            // for all individuals i with e[i] == 1 and active[i] = 1
            // move on link according to speed
            LogHelper.LogStep("Move A1.4");
            foreach (Population population in populationCollection.Where(x => x.e == 1 && x.active == 1))
            {
                LogHelper.LogStep("A1.4 MOVE for population group from demand node " + population.InitialNodeId, true);
                bool wasActive = true;
                population.Move();
                if (population.shelterReached)
                {
                    if (wasActive && population.active == 0)
                    {
                        population.UpdateTravelTime(environment.time);

                    }
                    else
                        if (wasActive)
                        {
                            MakeShelterDecision(population);
                        }
                }
                LogHelper.LogStep("A1.4 MOVE END ", true);
            }
        }

        internal int GetPopulationOnNode(Node n)
        {
            int sum = 0;
            var query = populationCollection.Where(x => x.CurrentNode == n && !x.shelterReached);
            if (query != null)
                sum = query.Sum(x => x.PeopleCount);
            return sum;
        }
        /// <summary>
        /// Get the group size configured
        /// </summary>
        public static int GROUPSIZE
        {
            get
            {
                return Int32.Parse(ConfigurationManager.AppSettings["GROUPSIZE"]);
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="z"></param>
        /// <returns></returns>
        internal int GetGroupsInZone(Zone z)
        {
            int sum = 0;
            var query = populationCollection.Where(x => x.CurrentNode.Zone == z);
            if (query != null)
                sum = query.Count();
            return sum;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="z"></param>
        /// <returns></returns>
        internal IEnumerable<Population> GetPopulationInitiallyInZone(Zone z)
        {
            var query = populationCollection.Where(x => this.environment.GetNode(x.InitialNodeId).Zone == z);
            return query;
        }
        #region export methods
        /// <summary>
        /// 
        /// </summary>
        public void ExportDS()
        {
            string directory = Utils.GetOutputDirectory();
            string filename = string.Format("{0}_{1}.{2}", Utils.FilePrefix, "DS", "csv");
            StreamWriter file = new StreamWriter(directory + filename, true);
            file.WriteLine(GetDSHeader());
            foreach (Population p in this.populationCollection)
            {
                Node initialNode = this.environment.GetNode(p.InitialNodeId);

                if (p.shelterReached)
                {
                    file.WriteLine(p.InitialNodeId + Utils.GetCSVDelimiter() + p.Shelter.Node.NodeID + Utils.GetCSVDelimiter() + p.TravelTime.ToString() + Utils.GetCSVDelimiter() + (initialNode != null ? initialNode.DepartureTime : -1).ToString() + Utils.GetCSVDelimiter() + p.Shelter.LastTravelTime.ToString());
                }
                else
                {
                    file.WriteLine(p.InitialNodeId + Utils.GetCSVDelimiter() + "" + Utils.GetCSVDelimiter() + p.TravelTime.ToString() + Utils.GetCSVDelimiter() + (initialNode != null ? initialNode.DepartureTime : -1).ToString() + Utils.GetCSVDelimiter() + "-1");
                }
            }
            file.Close();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private string GetDSHeader()
        {
            string header = "Demand_Node" + Utils.GetCSVDelimiter() + "Shelter_Node" + Utils.GetCSVDelimiter() + "Travel_Time" + Utils.GetCSVDelimiter() + "Departure_Time" + Utils.GetCSVDelimiter() + "Shelter_Last_Travel_Time";
            return header;
        }
        #endregion
    }
}

